package it.unicam.cs.cosy.bioshape.gui.visualizer3d;

import java.io.Serializable;
import java.util.Arrays;

import javax.vecmath.Matrix4d;

/**
 * Represents an instruction processed by the {@code VisualizationPanel} of the
 * simulator. It can represent respectively a:
 * <ul>
 * <li>
 * {@code #CREATE}: creation of an {@code Entity} in the visualiser;</li>
 * <li>
 * {@code #DESTROY}: destruction of an {@code Entity} in the visualiser.
 * Typically it is only <i>hidden</i> to avoid overhead in its recreation (if it
 * is necessary to show it again);</li>
 * <li>
 * {@code #MOVE}: moves the shape in the visualiser;</li>
 * <li>
 * {@code #JOIN}: glue together the given shapes;</li>
 * <li>
 * {@code #SPLIT}: split the given shape into the passed components (if the
 * split is a {@code StrongSplit}.</li>
 * </ul>
 * 
 * @author Federico Buti
 */
public class Command implements Serializable {

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + iteration;
        result = prime * result + Arrays.hashCode(matrices);
        result = prime * result + Arrays.hashCode(shapes);
        result = prime * result + type;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Command other = (Command) obj;
        if (iteration != other.iteration)
            return false;
        if (!Arrays.equals(matrices, other.matrices))
            return false;
        if (!Arrays.equals(shapes, other.shapes))
            return false;
        if (type != other.type)
            return false;
        return true;
    }

    /** Auto-generated Serial ID */
    private static final long serialVersionUID = 5204665635695736100L;

    /** Simulation iteration of this command */
    final public int iteration;

    /** Command which moves the shape */
    transient public final static int MOVE = 0;

    // /** Command which joins two or more shapes */
    // // USELESS
    // transient public final static int JOIN = 1;
    // /** Command which splits a shape */
    // // USELESS
    // transient public final static int SPLIT = 2;
    /** Command which adds a shape to the set of shapes */
    transient public final static int CREATE = 2;

    /** Command which removes a shape from the set shapes */
    transient public final static int DESTROY = 3;

    /** Specific type of this command */
    final public int type;

    /** Affine transformation matrices associated with this command */
    final public double[][] matrices;

    /** Shapes associated with this command */
    final public String[] shapes;

    /**
     * Builds a {@link #MOVE} command.
     * 
     * @param iteration
     *            Iteration in which the shape must be destroyed.
     * @param shape
     *            The shape to be moved.
     * @param m4d
     *            The transformation to be applied on the shape.
     */
    public Command(int iteration, String shape, Matrix4d m4d) {
        this.iteration = iteration;
        type = MOVE;
        shapes = new String[] { shape };
        matrices = new double[1][16];
        populateMatrix(m4d);
    }

    //
    // public Command(int iteration, String shape1, String shape2,
    // Matrix4d offset1, Matrix4d offset2, Matrix4d m4d) {
    // this.iteration = iteration;
    // type = JOIN;
    // shapes = new String[] { shape1, shape2 };
    // matrices = new double[3][16];
    // populateMatrix(new Matrix4d[] { offset1, offset2, m4d });
    // }
    //
    // public Command(int iteration, String[] shapes, Matrix4d[] m4ds) {
    // this.iteration = iteration;
    // type = SPLIT;
    // this.shapes = shapes;
    // matrices = new double[m4ds.length][16];
    // populateMatrix(m4ds);
    // }

    /**
     * Builds a {@link #CREATE} command.
     * 
     * @param iteration
     *            Iteration in which the shape must be destroyed.
     * @param shapeName
     *            The shape to be created.
     * @param geometryName
     *            The geometry to be used to create the shape.
     * @param initialOrientationAndPositionMatrix
     *            The transformation to be applied on the shape to position it
     *            in the simulation environment.
     */
    public Command(int iteration, String shapeName, String geometryName,
            Matrix4d initialOrientationAndPositionMatrix) {
        this.iteration = iteration;
        this.type = CREATE;
        this.shapes = new String[] { shapeName, geometryName };
        this.matrices = new double[1][16];
        populateMatrix(initialOrientationAndPositionMatrix);
    }

    // /**
    // * Builds a {@link #CREATE} command.
    // *
    // * @param iteration
    // * Iteration in which the shape must be destroyed.
    // * @param shape
    // * The shape to be created.
    // * @param geom
    // * The geometries to be used to create the shape.
    // * @param m4ds
    // * The transformations to be set over the shapes
    // */
    // public Command(int iteration, String shape, String[] geom, Matrix4d[]
    // m4ds) {
    // this.iteration = iteration;
    // type = CREATE;
    // this.shapes = new String[geom.length + 1];
    // shapes[0] = shape;
    // for (int i = 0; i < geom.length; i++)
    // shapes[i + 1] = geom[i];
    // matrices = new double[m4ds.length][16];
    // populateMatrix(m4ds);
    // }

    /**
     * Builds a {@link #DESTROY} command.
     * 
     * @param iteration
     *            Iteration in which the shape must be destroyed.
     * @param shape
     *            The shape to be destroyed.
     */
    public Command(int iteration, String shape) {
        this.iteration = iteration;
        type = DESTROY;
        shapes = new String[] { shape };
        matrices = null;
    }

    /**
     * Loads the matrices data structure with the passed {@code Matrix4d} to
     * transfer values in a more compact form.
     * 
     * @param m4ds
     *            {@code Matrix4d} objects to be stored.
     */
    private void populateMatrix(Matrix4d... m4ds) {
        int k = 0;
        int l = -1;
        for (Matrix4d m4d : m4ds) {
            l++;
            k = 0;
            for (int i = 0; i < 4; i++)
                for (int j = 0; j < 4; j++)
                    matrices[l][k++] = m4d.getElement(i, j);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        StringBuffer s = new StringBuffer("Command [iteration=" + iteration
                + ", type=" + type + "]\n");
        s.append("Matrices: \n");
        for (int i = 0; i < matrices.length; i++) {
            for (int j = 0; j < matrices[0].length; j++)
                s.append(" " + matrices[i][j]);
            s.append("\n");
        }
        s.append("Shapes: \n");
        for (int i = 0; i < shapes.length; i++) {
            s.append(" " + shapes[i]);
        }
        s.append("\n");
        return s.toString();
    }

}